import tkinter as tk
from tkinter import ttk, filedialog, messagebox
import struct

class NPDMParserGUI:
    def __init__(self, root):
        self.root = root
        self.root.title("NPDM 解析器")
        self.root.geometry("1000x600")

        self.file_path = ""
        self.file_data = b""

        # 存储字段长度，用于高亮
        self.field_lengths = {}

        self.create_widgets()

    def create_widgets(self):
        toolbar = tk.Frame(self.root)
        toolbar.pack(side=tk.TOP, fill=tk.X)

        tk.Button(toolbar, text="选择 NPDM 文件", command=self.load_file).pack(side=tk.LEFT, padx=5, pady=5)
        tk.Button(toolbar, text="导出解析结果", command=self.export_result).pack(side=tk.LEFT, padx=5, pady=5)

        main_pane = tk.PanedWindow(self.root, orient=tk.HORIZONTAL, sashrelief=tk.RAISED)
        main_pane.pack(fill=tk.BOTH, expand=True)

        # 左侧分页解析区
        self.tab_control = ttk.Notebook(main_pane, width=450)
        main_pane.add(self.tab_control)

        # 右侧十六进制显示区
        right_frame = tk.Frame(main_pane)
        main_pane.add(right_frame, stretch="always")

        self.hex_text = tk.Text(right_frame, wrap=tk.NONE)
        self.hex_text.pack(fill=tk.BOTH, expand=True, side=tk.LEFT)

        scrollbar = tk.Scrollbar(right_frame, command=self.hex_text.yview)
        scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
        self.hex_text.config(yscrollcommand=scrollbar.set)

        self.status_var = tk.StringVar()
        status_bar = tk.Label(self.root, textvariable=self.status_var, bd=1, relief=tk.SUNKEN, anchor=tk.W)
        status_bar.pack(side=tk.BOTTOM, fill=tk.X)

    def load_file(self):
        path = filedialog.askopenfilename(filetypes=[("NPDM Files", "*.npdm")])
        if not path:
            return
        self.file_path = path
        with open(path, "rb") as f:
            self.file_data = f.read()

        self.status_var.set(f"文件: {self.file_path} | 大小: {len(self.file_data)} bytes | 解析成功")

        self.show_hex()
        self.parse_meta_tab()

    def show_hex(self):
        self.hex_text.delete(1.0, tk.END)
        for i in range(0, len(self.file_data), 16):
            chunk = self.file_data[i:i+16]
            hex_str = ' '.join(f"{b:02X}" for b in chunk)
            self.hex_text.insert(tk.END, f"{i:08X}: {hex_str}\n")

    def parse_meta_tab(self):
        # 清空已有 Tab
        for tab in self.tab_control.winfo_children():
            tab.destroy()

        meta_tab = ttk.Frame(self.tab_control)
        self.tab_control.add(meta_tab, text="META")

        columns = ("offset", "field", "desc", "size", "value")
        tree = ttk.Treeview(meta_tab, columns=columns, show="headings", height=20)
        tree.heading("offset", text="偏移")
        tree.heading("field", text="字段名")
        tree.heading("desc", text="描述")
        tree.heading("size", text="大小")
        tree.heading("value", text="值")
        tree.column("offset", width=60)
        tree.column("field", width=100)
        tree.column("desc", width=150)
        tree.column("size", width=50)
        tree.column("value", width=150)
        tree.pack(fill=tk.BOTH, expand=True)

        if len(self.file_data) < 0x80:
            messagebox.showerror("错误", "文件太小，无法解析 META")
            return

        meta_data = self.file_data[:0x80]

        fields = [
            (0x00, "Magic", "META 魔数", 4, f"0x{meta_data[:4].hex().upper()}"),
            (0x04, "SignatureKeyGen", "签名密钥生成", 4, f"0x{struct.unpack('<I', meta_data[4:8])[0]:08X}"),
            (0x08, "Reserved", "保留", 4, f"0x{meta_data[8:12].hex().upper()}"),
            (0x0C, "Flags", "标志", 1, f"0x{meta_data[12]:02X}"),
            (0x0E, "MainThreadPriority", "主线程优先级", 1, f"0x{meta_data[14]:02X}"),
            (0x0F, "MainThreadCoreNumber", "主线程核心编号", 1, f"0x{meta_data[15]:02X}"),
            (0x14, "SystemResourceSize", "系统资源大小", 4, f"0x{struct.unpack('<I', meta_data[20:24])[0]:08X}"),
            (0x18, "Version", "版本", 4, f"0x{struct.unpack('<I', meta_data[24:28])[0]:08X}"),
            (0x1C, "MainThreadStackSize", "主线程栈大小", 4, f"0x{struct.unpack('<I', meta_data[28:32])[0]:08X}"),
            (0x20, "Name", "应用名称", 16, meta_data[32:48].decode("utf-8", errors="ignore").strip()),
            (0x30, "ProductCode", "产品代码", 16, meta_data[48:64].decode("utf-8", errors="ignore").strip()),
            (0x70, "AciOffset", "ACI 偏移", 4, f"0x{struct.unpack('<I', meta_data[112:116])[0]:08X}"),
            (0x74, "AciSize", "ACI 大小", 4, f"0x{struct.unpack('<I', meta_data[116:120])[0]:08X}"),
            (0x78, "AcidOffset", "ACID 偏移", 4, f"0x{struct.unpack('<I', meta_data[120:124])[0]:08X}"),
            (0x7C, "AcidSize", "ACID 大小", 4, f"0x{struct.unpack('<I', meta_data[124:128])[0]:08X}"),
        ]

        self.field_lengths.clear()
        for offset, field, desc, size, value in fields:
            tree.insert("", tk.END, values=(f"0x{offset:02X}", field, desc, size, value))
            self.field_lengths[offset] = size

        # 点击高亮
        def on_tree_select(event):
            selected = tree.selection()
            if not selected:
                return
            item = tree.item(selected[0])
            offset_hex = item["values"][0]
            field_offset = int(offset_hex, 16)
            length = self.field_lengths.get(field_offset, 4)
            self.highlight_bytes(field_offset, length)

        tree.bind("<<TreeviewSelect>>", on_tree_select)

        # 右键复制值
        self.setup_treeview_copy(tree)

    def highlight_bytes(self, offset, length):
        self.hex_text.tag_remove("highlight", "1.0", tk.END)

        start_line = offset // 16 + 1
        start_byte_in_line = offset % 16
        end_offset = offset + length
        end_line = end_offset // 16 + 1
        end_byte_in_end_line = end_offset % 16

        for line in range(start_line, end_line + 1):
            line_start_index = f"{line}.0"
            line_text = self.hex_text.get(line_start_index, f"{line}.end")

            if line == start_line:
                col_start = 9 + start_byte_in_line * 3
            else:
                col_start = 9

            if line == end_line:
                col_end = 9 + end_byte_in_end_line * 3
            else:
                col_end = len(line_text)

            self.hex_text.tag_add("highlight", f"{line}.{col_start}", f"{line}.{col_end}")

        self.hex_text.tag_config("highlight", background="yellow")
        self.hex_text.see(f"{start_line}.0")

    def setup_treeview_copy(self, tree):
        menu = tk.Menu(tree, tearoff=0)

        def copy_selected():
            selected = tree.selection()
            if not selected:
                return
            item = tree.item(selected[0])
            value = item['values'][-1]
            tree.clipboard_clear()
            tree.clipboard_append(str(value))
            tree.update()

        menu.add_command(label="复制值", command=copy_selected)

        def on_right_click(event):
            row_id = tree.identify_row(event.y)
            if row_id:
                tree.selection_set(row_id)
                menu.post(event.x_root, event.y_root)

        tree.bind("<Button-3>", on_right_click)

    def export_result(self):
        messagebox.showinfo("提示", "导出功能暂未实现")

if __name__ == "__main__":
    root = tk.Tk()
    app = NPDMParserGUI(root)
    root.mainloop()
